package cn.itcast.nems.approval.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import cn.itcast.ic.common.bean.Page;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.ic.common.exception.v2.bean.BizStatusEnum;
import cn.itcast.nems.approval.dao.entity.ApprovalAdditionalForm;
import cn.itcast.nems.approval.dao.entity.ApprovalConfig;
import cn.itcast.nems.approval.dao.entity.ApprovalForm;
import cn.itcast.nems.approval.dao.entity.ApprovalFormFlow;
import cn.itcast.nems.approval.dao.mapper.ApprovalFormMapper;
import cn.itcast.nems.approval.enumeration.ApprovalErrorBodyEnum;
import cn.itcast.nems.approval.enumeration.ApprovalFormFlowNodeEventEnum;
import cn.itcast.nems.approval.enumeration.ApprovalFormStateEnum;
import cn.itcast.nems.approval.service.ApprovalAdditionalFormService;
import cn.itcast.nems.approval.service.ApprovalConfigService;
import cn.itcast.nems.approval.service.ApprovalFormFlowService;
import cn.itcast.nems.approval.service.ApprovalFormService;
import cn.itcast.nems.common.PageQueryModel;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.common.entity.BaseEntity;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.service.BizNumberService;
import cn.itcast.nems.common.util.LocalDateTimeUtils;
import cn.itcast.nems.manager.activity.BpmWorkflowManager;
import cn.itcast.nems.manager.redis.RedisUtil;
import cn.itcast.nems.studentrecord.dao.dataobject.ApprovalFormQueryModel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itcast.activiti.sdk.constant.AttachmentTypeConstant;
import com.itcast.activiti.sdk.domain.Attachment;
import com.itcast.activiti.sdk.domain.TaskStart;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class ApprovalFormServiceImpl extends ServiceImpl<ApprovalFormMapper, ApprovalForm> implements ApprovalFormService {

    final ApprovalFormFlowService approvalFormFlowService;
    final ApprovalConfigService approvalConfigService;
    final BpmWorkflowManager bpmWorkflowManager;
    final ApprovalFormMapper approvalFormMapper;
    final BizNumberService bizNumberService;
    final ApprovalAdditionalFormService approvalAdditionalFormService;
    final RedisUtil redisUtil;
    private final String REDIS_BPM_ACTIVITI_BACK_V2_PREFIX = "NEMS:BPM:ACTIVITI_BACK_V2:TIME";
    @Value("${spring.profiles.active}")
    private String ACTIVE;

    @Override
    public List<ApprovalForm> getWaitApprovalFormList(ApprovalFormQueryModel queryModel) {
        return approvalFormMapper.getWaitApprovalFormList(queryModel);
    }

    @Override
    @Transactional
    public String start(final JournalType journalType, final String journalId, final Map<String, Object> variables, String passbackParam) {
        // 同一个凭单只允许有一个正在进行中的审批流程
        var formList = getActiveByJournalId(journalType, journalId, Arrays.asList(ApprovalFormStateEnum.PENDING, ApprovalFormStateEnum.APPROVING, ApprovalFormStateEnum.CANCELED));
//        Assert.isTrue(CollectionUtil.isEmpty(formList), BizExceptionProducer.produce(ApprovalErrorBodyEnum.APPROVAL_FORM_ACTIVE_CONFLICT, journalType.getCnName(), journalId));
        if (!formList.isEmpty()) {
            ApprovalForm approvalForm = formList.get(0);
            LocalDateTime modifiedDatetime = approvalForm.getCreatedDatetime();
            String formID = approvalForm.getId();
            // TODO 兼容老版本，历史数据还是从启流程
            Object object = redisUtil.get(REDIS_BPM_ACTIVITI_BACK_V2_PREFIX);
            if (object != null) {
                if (modifiedDatetime.isAfter(LocalDateTimeUtils.parse((String) object))) {
                    // 从新提交
                    this.approve(formID, ApproveResult.APPROVE, "申请", null, 1, variables);
                    return formID;
                }
            } else {
                redisUtil.set(REDIS_BPM_ACTIVITI_BACK_V2_PREFIX, LocalDateTimeUtils.formatLocalDateTime(LocalDateTime.now()));
            }
        }
        // 1.获取当前凭单对应的BPM流程KEY（每个凭单对应的审批流程会不一样）
        ApprovalConfig config = approvalConfigService.getByJournalCode(journalType.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, journalType.getCnName()));

        // 2.创建本地流程审批单，状态为“待审批｜PENDING”
        // 审批流程标题，审批表单名称-学生姓名-业务凭单号。示例：升级凭单-张三-SJ96UA33
        /**
         * TODO 查找学生姓名：
         * 1、通过审批单查找对应凭单
         * 2、通过凭单上的原始学籍ID，查找对应的学生
         */
        String title = journalType.getCnName().concat("-").concat(journalId);
        String formId = IdUtil.fastSimpleUUID();
        ApprovalForm approvalForm = new ApprovalForm();
        approvalForm.setId(formId);
        approvalForm.setTitle(title);
        approvalForm.setBpmProcessKey(config.getBpmProcessKey());
        approvalForm.setBpmBizKey(formId);
        approvalForm.setBpmBizLine(config.getBpmBizLine());
        approvalForm.setBpmBizVariables(JSONUtil.toJsonStr(variables));
        approvalForm.setJournalType(journalType.name());
        approvalForm.setJournalId(journalId);
        approvalForm.setState(ApprovalFormStateEnum.PENDING.name());
        approvalFormMapper.insert(approvalForm);

        createFlowEvent(formId, ApprovalFormFlowNodeEventEnum.STARTED);

        String currentUserMail = AccountHolder.getRequiredAccountInfo().getEmail();
        // 确认审批流程单跳转链接
        String msgLink = config.getBpmMessageUrl() + journalId;

        //  4.组织审批参数，调用BPM SDK发起审批流
        try {
            bpmWorkflowManager.startProcess(currentUserMail, title, formId, config.getBpmProcessKey(), ACTIVE, msgLink, variables, List.of(BpmWorkflowManager.SendMessageType.WECOM));
        } catch (Exception e) {
            log.error(String.format("提交%s失败！发起流程审批时出现问题。BPM返回异常消息：%s", journalType.getCnName(), e.getMessage()), e);
            BizExceptionProducer.throwProduce(e, NemsErrorBodyEnum.BPM_START_PROCESS_FAILD);
        }
        return formId;
    }

    @Override
    @Transactional
    public void cancel(final String formID) {
        if (!redisUtil.hasKey(REDIS_BPM_ACTIVITI_BACK_V2_PREFIX)) {
            redisUtil.set(REDIS_BPM_ACTIVITI_BACK_V2_PREFIX, LocalDateTimeUtils.formatLocalDateTime(LocalDateTime.now()));
        }
        // 1.调用BPM“撤回”接口
        try {
            Object response = bpmWorkflowManager.cancel(formID, AccountHolder.getRequiredAccountInfo().getEmail());
            log.debug("【调用工作流撤回接口】响应正文：{}", JSON.toJSONString(response));
        } catch (Exception e) {
            BizExceptionProducer.throwBadRequest(e, BizStatusEnum.BAD_REQUEST.toString(), "%s", e.getMessage());
        }
        // 2.更新对应流程审批单的状态为“已撤回｜CANCELED”
        int updateResult = approvalFormMapper.update(null, new LambdaUpdateWrapper<ApprovalForm>()
                .set(ApprovalForm::getState, ApprovalFormStateEnum.CANCELED.name())
//                .set(ApprovalForm::getDeletedFlag, Boolean.TRUE)
                .eq(ApprovalForm::getId, formID));
        Assert.isTrue(updateResult > 0, BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, "撤回"));
        // 3.创建本地“流程审批流”，事件类型为“撤回｜CANCELED”，创建人为撤回操作人的工作邮箱。
        createFlowEvent(formID, ApprovalFormFlowNodeEventEnum.CANCELED);
        approvalAdditionalFormService.delete(formID);
    }

    @Override
    @Transactional
    public void rollbackRequester(final String formID) {
        // 3.创建本地“流程审批流”，事件类型为“撤回｜CANCELED”，创建人为撤回操作人的工作邮箱。
        createFlowEvent(formID, ApprovalFormFlowNodeEventEnum.ROLLBACK_REQUESTER);
    }

    @Override
    @Transactional
    public void rollbackPrevious(final String formID) {
        // 3.创建本地“流程审批流”，事件类型为“撤回｜CANCELED”，创建人为撤回操作人的工作邮箱。
        createFlowEvent(formID, ApprovalFormFlowNodeEventEnum.ROLLBACK_PREVIOUS);
    }

    @Override
    @Transactional
    public void approve(final String formID, final ApproveResult result, final String comment, final String formJson, Integer applyType) {
        approve(formID, result, comment, formJson, applyType, null);
    }

    @Override
    @Transactional
    public void approve(final String formID, final ApproveResult result, final String comment, final String formJson, Integer applyType, Map<String, Object> variables) {
        log.debug("【审批】请求参数 formID：{}，result：{}，comment：{}，formJson：{}", formID, result, comment, formJson);
        String currentUserMail = AccountHolder.getRequiredAccountInfo().getEmail();
        /**
         * 注：只处理单个节点的审批通过，以及全部流程的审批不过（结束）。如果当前节点审批通过后，全部流程审核完毕，后续的处理由BPM的事件处理解决。
         *
         */
        // 1. 调用BPM审批接口
        try {
            // 当前审批节点信息
            List taskNodes = null;
            TaskStart taskStart = TaskStart.builder()
                    .audit(result == ApproveResult.APPROVE ? Boolean.TRUE : Boolean.FALSE)
                    .comment(comment)
                    .passbackParam(ACTIVE)
                    .build();
            if (applyType != null) {
                taskStart.setApplyType(applyType);
            }
            if (variables != null) {
                taskStart.setVariables((HashMap<String, Object>) variables);
            }
            if (StringUtils.hasText(formJson)) {
                // 添加到工作流表单附件信息
                Attachment attachment = new Attachment(AttachmentTypeConstant.JSON, "凭单补充信息", formID, formJson);
                taskStart.setAttachment(attachment);
                // 获取当前审批节点信息(注意：必须审批前获取)
                Object findTaskNodeInfo = bpmWorkflowManager.findFindTaskNodeInfo(formID, currentUserMail);
                if (findTaskNodeInfo != null) {
                    taskNodes = (List) findTaskNodeInfo;
                }
            }
            String auditResult = bpmWorkflowManager.audit(formID, currentUserMail, taskStart);
            log.debug("【审批】调用工作流审批接口 响应正文：{}", auditResult);
            // TODO 补充信息表入库
            this.createApprovaljournalform(formID, currentUserMail, auditResult, taskNodes, formJson);
        } catch (Exception e) {
            BizExceptionProducer.throwBadRequest(e, BizStatusEnum.BAD_REQUEST.toString(), "%s", e.getMessage());
        }

        // 2. 更新对应流程审批单的状态为 “审批中｜APPROVING” 或 “审批未通过｜REJECTED”
        ApprovalFormStateEnum state = result == ApproveResult.APPROVE ? ApprovalFormStateEnum.APPROVING : ApprovalFormStateEnum.REJECTED;
        // // 1：审批通过（默认））2：驳回到发起人）3：驳回上一级）
        if (applyType != null && applyType == 2) {
            state = ApprovalFormStateEnum.CANCELED;
        }
        approvalFormMapper.update(null, new LambdaUpdateWrapper<ApprovalForm>().set(ApprovalForm::getState, state.name()).eq(ApprovalForm::getId, formID));

        // 3. 创建本地“流程审批流”，事件类型为“当前节点审批通过｜APPROVED” 或 “审批未通过｜REJECTED”，创建人为节点审批人的工作邮箱。
        createFlowEvent(formID, result == ApproveResult.APPROVE ? ApprovalFormFlowNodeEventEnum.APPROVED : ApprovalFormFlowNodeEventEnum.REJECTED, comment);
    }

    // TODO 补充信息表入库
    private void createApprovaljournalform(String formID, String currentUserMail, String auditResult, List taskNodes, String form) {
        if (!StringUtils.hasText(form)) {
            return;
        }
        if (!StringUtils.hasText(auditResult)) {
            return;
        }
        if (taskNodes == null || taskNodes.size() == 0) {
            return;
        }
        LinkedHashMap taskNode = (LinkedHashMap) taskNodes.get(0);
        if (taskNode == null) {
            return;
        }
        if (!taskNode.containsKey("nodeKey")) {
            return;
        }
        ApprovalForm approvalForm = this.getById(formID);
        if (approvalForm == null) {
            log.info("【审批-补充信息】未匹配到数据：" + formID);
            return;
        }
        String nodeKey = (String) taskNode.get("nodeKey");
        String journalId = approvalForm.getJournalId();
        String journalType = approvalForm.getJournalType();
        String bpmBizKey = approvalForm.getBpmBizKey();
        JSONArray formJsons = JSON.parseArray(form);
        // 验证是否有重复field_key
        this.checkDuplicates(formJsons);
        LocalDateTime createdDatetime = LocalDateTime.now();
        for (int i = 0; i < formJsons.size(); i++) {
            JSONObject formJson = formJsons.getJSONObject(i);
            String key = formJson.getString("field_key");
            String value = formJson.getString("field_value");
            String name = formJson.getString("field_name");

            ApprovalAdditionalForm params = new ApprovalAdditionalForm();
            params.setFieldName(name);
            params.setFieldKey(key);
            params.setFieldValue(value);
            params.setFormId(formID);
            params.setFormBpmBizKey(bpmBizKey);
            params.setJournalType(journalType);
            params.setJournalId(journalId);
            params.setBpmNodeKey(nodeKey);
            params.setCreatedBy(currentUserMail);
            params.setCreatedDatetime(createdDatetime);
            params.setModifiedBy(currentUserMail);
            approvalAdditionalFormService.create(params);
        }
    }

    // 验证是否有重复field_key
    private static void checkDuplicates(JSONArray jsonArray) {
        HashSet<String> nodes = new HashSet<>();
        boolean hasDuplicates = false;
        String fieldKey = "";
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = (JSONObject) jsonArray.get(i);
            if (!nodes.add(jsonObject.getString("field_key"))) {
                hasDuplicates = true;
                fieldKey = jsonObject.getString("field_key");
                break;
            }
        }
        if (hasDuplicates) {
            throw new RuntimeException("审批失败！表单字段key重复：" + fieldKey);
        }
    }

    @Override
    @Transactional
    public String remind(final String formID, final String targetAssignee) {
        Assert.notBlank(formID);

        final String currentUserMail = AccountHolder.getRequiredAccountInfo().getEmail();
        // 1. 调用BPM的催办
        Object result = null;
        try {
            result = bpmWorkflowManager.reminder(formID, currentUserMail, targetAssignee);
        } catch (Exception e) {
            log.info("【催办】formID：{}，targetAssignee：{}，异常：{}", formID, targetAssignee, e.getMessage());
            BizExceptionProducer.throwBadRequest(e, BizStatusEnum.BAD_REQUEST.toString(), "%s", e.getMessage());
        }

        // 2. 保存本地事件
        createFlowEvent(formID, ApprovalFormFlowNodeEventEnum.REMINDED);
        return JSON.toJSONString(result);
    }

    @Override
    @Transactional
    public void finish(String formID) {
        approvalFormMapper.update(null, new LambdaUpdateWrapper<ApprovalForm>()
                .set(ApprovalForm::getState, ApprovalFormStateEnum.FINISHED)
                // .set(ApprovalForm::getFormDescription, "")
                .eq(ApprovalForm::getId, formID));

        // 2. 保存本地事件
        createFlowEvent(formID, ApprovalFormFlowNodeEventEnum.FINISHED);
    }

    @Override
    @Transactional
    public void fail(String formID, final String message) {
        approvalFormMapper.update(null, new LambdaUpdateWrapper<ApprovalForm>()
                .set(ApprovalForm::getState, ApprovalFormStateEnum.REJECTED)
                .set(ApprovalForm::getFormDescription, message)
                .eq(ApprovalForm::getId, formID));

        createFlowEvent(formID, ApprovalFormFlowNodeEventEnum.REJECTED, message);
    }

    @Override
    public void updateDescription(final String formId, final String message) {
        approvalFormMapper.updateFormDescription(formId, message);
    }

    @Override
    public ApprovalForm getByBusinessKey(String bpmBusinesskey) {
        return approvalFormMapper.selectOne(new LambdaQueryWrapper<ApprovalForm>()
                .eq(ApprovalForm::getBpmBizKey, bpmBusinesskey)
                .last(" limit 1 "));
    }

    @Override
    public List<ApprovalForm> getActiveByJournalId(JournalType journalType, String journalId, List<ApprovalFormStateEnum> stateList) {
        LambdaQueryWrapper<ApprovalForm> wrapper = new LambdaQueryWrapper<ApprovalForm>()
                .eq(ApprovalForm::getJournalType, journalType.name())
                .eq(ApprovalForm::getJournalId, journalId);
        if (CollectionUtil.isNotEmpty(stateList)) {
            wrapper.in(ApprovalForm::getState, stateList);
        }
        wrapper.orderByDesc(BaseEntity::getModifiedDatetime);
        return approvalFormMapper.selectList(wrapper);
    }

    @Override
    public List<String> getFormIDs(String formID) {
        return approvalFormMapper.getFormIDs(formID);
    }

    private void createFlowEvent(final String formID, final ApprovalFormFlowNodeEventEnum event) {
        createFlowEvent(formID, event, "");
    }

    private void createFlowEvent(final String formID, final ApprovalFormFlowNodeEventEnum event, final String comment) {
        ApprovalFormFlow approvalFormFlow = new ApprovalFormFlow();
        approvalFormFlow.setFormId(formID);
        approvalFormFlow.setNodeEvent(event.name());
        approvalFormFlow.setNodeComment(comment);
        approvalFormFlow.setCreatedBy(AccountHolder.getRequiredAccountInfo().getEmail());
        approvalFormFlowService.save(approvalFormFlow);
    }

    @Override
    public List<ApprovalForm> getMyApprovedForms(@NonNull JournalType journalType, @NonNull String accountEmail) {
        return super.getBaseMapper().getMyApprovedForms(journalType.name(), accountEmail);
    }

    @Override
    public Page<ApprovalForm> listMyTodo(PageQueryModel<?> page, JournalType journalType) {
        ApprovalConfig config = approvalConfigService.getByJournalCode(journalType.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, journalType.getCnName()));

        Page<ApprovalForm> rst = null;
        List<String> formIds = bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccountInfo().getEmail());
        if (CollectionUtil.isNotEmpty(formIds)) {
            LambdaQueryWrapper<ApprovalForm> queryWrapper = new LambdaQueryWrapper<ApprovalForm>()
                    .eq(ApprovalForm::getJournalType, journalType.name())
                    .in(ApprovalForm::getBpmBizKey, formIds);

            IPage<ApprovalForm> mpage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<ApprovalForm>()
                    .setCurrent(page.getPageNumber())
                    .setSize(page.getPageSize());
            List<ApprovalForm> records = this.list(mpage, queryWrapper);
            // TODO records 做类型转换，不需要这么多字段

            rst = Page.of(page.getPageNumber(), page.getPageSize(), (int) mpage.getTotal(), records);
        }
        return rst;
    }

    @Override
    public List<String> listWaitApprovalIds(JournalType journalType) {
        ApprovalConfig config = approvalConfigService.getByJournalCode(journalType.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, journalType.getCnName()));
        var formIdList = bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccount());
        Map<String, String> JournalIdFormIdMap = new HashMap<>();
        if (!formIdList.isEmpty()) {
            List<ApprovalForm> list = lambdaQuery().in(ApprovalForm::getId, formIdList).list(com.baomidou.mybatisplus.extension.plugins.pagination.Page.of(1, 1000));
            if (list == null || list.size() == 0) {
                return null;
            }
            JournalIdFormIdMap = list.stream().collect(Collectors.toMap(ApprovalForm::getJournalId, ApprovalForm::getId));
            List<String> ids = JournalIdFormIdMap.keySet().stream().toList();
            return ids;
        }
        return null;
    }

    @Override
    public List<String> getJournalIdFormIds(JournalType journalType) {
        ApprovalConfig config = approvalConfigService.getByJournalCode(journalType.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, journalType.getCnName()));
        return bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccount());
    }

    @Override
    public Integer getMyTodoCount(JournalType journalType) {
        var config = approvalConfigService.getByJournalCode(journalType.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, journalType.getCnName()));

        List<String> formIds = bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccountInfo().getEmail());
        return CollectionUtils.isEmpty(formIds) ? 0 : formIds.size();
    }
}
